﻿using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace CacheExtensions
{
    public class MemoryCache : ICache
    {

        private readonly IMemoryCache _memoryCache;
        public MemoryCache(IMemoryCache memoryCache)
        {
            _memoryCache = memoryCache;
        }

        /*public IEnumerable<string> GetAllKeys()
        {
 
            const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
            var entries = _memoryCache.GetType().GetField("_entries", flags)?.GetValue(_memoryCache);
            var keys = new List<string>();
            if (entries != null)
            {
                var cacheItems = entries as IDictionary;
                if (cacheItems != null)
                {
                    foreach (DictionaryEntry cacheItem in cacheItems)
                    {
                        keys.Add(cacheItem.Key?.ToString() ?? "");
                    }
                }
            }
            return keys;
        }*/
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IEnumerable<string> GetAllKeys()
        {
            const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
            var coherentState = _memoryCache.GetType().GetField("_coherentState", flags)?.GetValue(_memoryCache);//增加一个获取CoherentState对象环节
            var entries = coherentState?.GetType().GetField("_stringEntries", flags)?.GetValue(coherentState);
            var cacheItems = entries as IDictionary;
            var keys = new List<string>();
            if (cacheItems == null) return keys;
            foreach (DictionaryEntry cacheItem in cacheItems)
            {
                keys.Add(cacheItem.Key?.ToString() ?? "");
            }
            return keys;
        }


        #region Remove
        public long Remove(params string[] key)
        {
            foreach (var k in key)
            {
                _memoryCache.Remove(k);
            }
            return key.Length;
        }

        public Task<long> RemoveAsync(params string[] key)
        {
            foreach (var k in key)
            {
                _memoryCache.Remove(k);
            }

            return Task.FromResult((long)key.Length);
        }


        public async Task<long> RemoveByPatternAsync(string pattern)
        {
            if (string.IsNullOrWhiteSpace(pattern))
                return default;

            // pattern = Regex.Replace(pattern, @"\{.*\}", "(.*)");
            pattern = pattern.Replace("*", "");
            pattern = $"^{pattern}\\S+$|^{pattern}";
            var keys = GetAllKeys().Where(k => Regex.IsMatch(k, pattern));
            //^aa\\S+$|^aa$
            if (keys != null && keys.Count() > 0)
            {
                return await RemoveAsync(keys.ToArray());
            }

            return default;
        }

        public async Task<long> RemoveStartWithAsync(string str)
        {
            if (string.IsNullOrWhiteSpace(str))
                return default;
            var keys = GetAllKeys().Where(k => k.StartsWith(str));
            //^aa\\S+$|^aa$
            if (keys != null && keys.Count() > 0)
            {
                return await RemoveAsync(keys.ToArray());
            }
            return default;
        }

        public async Task<IEnumerable<string>> GetKeys(string pattern)
        {
            if (string.IsNullOrWhiteSpace(pattern))
                return null;

            pattern = Regex.Replace(pattern, @"\{.*\}", "(.*)");

            var keys = GetAllKeys().Where(k => Regex.IsMatch(k, pattern));
            return keys;
        }
        #endregion

        #region TryGet
        public bool Exists(string key)
        {
            return _memoryCache.TryGetValue(key, out _);
        }

        public Task<bool> ExistsAsync(string key)
        {
            return Task.FromResult(_memoryCache.TryGetValue(key, out _));
        }
        #endregion

        #region Get
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public object? Get(string key)
        {
            var value = _memoryCache.Get(key)?.ToString() ?? "";


            if (string.IsNullOrWhiteSpace(value))
            {
                return null;
            }
            var cacheItem = JSON.Deserialize<CacheItem>(value) ?? throw new Exception("CacheItem value can't be empty");

            var valueType = Type.GetType(cacheItem.AssemblyQualifiedName, true, true) ?? throw new Exception("type can't be empty");


            /*  if (cacheItem.AssemblyQualifiedName == typeof(string).AssemblyQualifiedName)
              {
                  return cacheItem.Value?.ToString();
              }*/

            return JSON.Deserialize(cacheItem.Value?.ToString() ?? "", valueType);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public T? Get<T>(string key)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }
            var o = this.Get(key);
            return o == null ? default(T) : (T)o;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public Task<object?> GetAsync(string key)
        {
            return Task.FromResult(Get(key));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public Task<T?> GetAsync<T>(string key)
        {
            var o = Get(key);
            if (o == null)
            {
                return Task.FromResult(default(T));
            }
            return Task.FromResult((T?)o);
        }
        #endregion

        #region Set
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public bool Set(string key, object value)
        {

            CacheItem cacheItem = new CacheItem()
            {
                // AssemblyName = value.GetType().Assembly.FullName ?? throw new Exception("assembly.fullName 不能为空"),
                AssemblyQualifiedName = value.GetType().AssemblyQualifiedName ?? throw new Exception("type AssemblyQualifiedName can't be empty"),
                Value = JSON.Serialize(value),
            };

            _memoryCache.Set(key, JSON.Serialize(cacheItem));
            return true;

        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expire"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public bool Set(string key, object value, TimeSpan expire)
        {

            CacheItem cacheItem = new CacheItem()
            {
                // AssemblyName = value.GetType().Assembly.FullName ?? throw new Exception("assembly.fullName 不能为空"),
                AssemblyQualifiedName = value.GetType().AssemblyQualifiedName ?? throw new Exception("type AssemblyQualifiedName can't be empty"),
                Value = JSON.Serialize(value),
            };

            _memoryCache.Set(key, JSON.Serialize(cacheItem), expire);
            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public Task<bool> SetAsync(string key, object value)
        {
            Set(key, value);
            return Task.FromResult(true);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expire"></param>
        /// <returns></returns>
        public Task<bool> SetAsync(string key, object value, TimeSpan expire)
        {
            Set(key, value, expire);
            return Task.FromResult(true);
        }

        #endregion


    }
}
